﻿using System.IO;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using System.Xml.XPath;
using Tridion.ContentManager.CoreService.Client;
using System.Text;

namespace PowerTools.ComponentSynchroniser
{
    
    public class ComponentSynchroniser
    {
        public readonly ISchemaFactory schemaFactory;
        public readonly IComponentFactory componentFactory;
        public readonly ISessionAwareCoreService coreService;
        
        public ComponentSynchroniser(ISessionAwareCoreService coreService){
            this.coreService = coreService;
            this.schemaFactory = new CachingSchemaFactory(this.coreService);
            this.componentFactory = new ComponentFactory(this.coreService);
        }

        public void Synchronise(string schemaID, string componentID)
        {
            XDocument schema = this.schemaFactory.GetSchema(schemaID);
            XDocument component = this.componentFactory.GetComponent(componentID);
            XDocument transformed = this.TransformDocumentToResult(component, schemaID);
            ComponentData updated = new ComponentData();

            updated.Id = componentID;
            updated.Content = transformed.Element(Ns.tcm + "Component").Element(Ns.tcm + "Data").Element(Ns.tcm + "Content").ToString();
            updated.Metadata = transformed.Element(Ns.tcm + "Component").Element(Ns.tcm + "Data").Element(Ns.tcm + "Metadata").ToString();

            coreService.Update(updated, new ReadOptions());
        }

        private XslCompiledTransform _transform;
        /// <summary>
        /// This is intended mostly to make things testable. It's your responsibility to ensure that the transform is loaded 
        /// with settings to support document() if you use document() etc.... 
        /// </summary>
        public XslCompiledTransform Transform
        {
            set { _transform = value; }
            get { return this._transform ?? (this._transform = GetTransformByResourceName("PowerTools.TransformToSynchronisedComponent.xslt"));}
        }

        private XmlResolver _transformResolver;
        public XmlResolver TransformResolver 
        {
            set { this._transformResolver = value; }
            get { return this._transformResolver ?? (this._transformResolver = new TcmResolver(this.coreService));}
        }

        private static XslCompiledTransform GetTransformByResourceName(string resourceName) 
        {
            //TODO take away the debug flag
            XslCompiledTransform transform = new XslCompiledTransform(true);
            
            using (Stream manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
            using (XmlTextReader reader = new XmlTextReader(manifestResourceStream))
            {
                XsltSettings settings = new XsltSettings(true, true);
                transform.Load(reader, settings, new XmlUrlResolver());
            }
            return transform;
        }

        private XDocument _defaultsAndOverrides;
        public XDocument DefaultsAndOverrides
        {
        // Accepts an XML document that looks a bit like this. It mirrors the schema structure, though is namespaceless
        // In the Old Synchroniser, this was generated by a javascript function looping through the hidden controls and "indenting" 
        // based on start_ and end_ naming conventions in the control names. The hidden controls in turn are generated by an XSLT that 
        // generates output based on the schema fields. We'll probably copy chunks of this logic wholesale, and the data layout still seems
        // reasonable (i.e. general enough for use when not coupled to the extension), so for now, we'll just do it exactly the same.
                //<root>
                //  <Content>
                //    <embedded1>
                //      <plainText overrule="true">e1pt</plainText>
                //      <embedded2>
                //        <plainText overrule="true">e2pt</plainText>
                //        <coloursFromCategory overrule="true">Red</coloursFromCategory>
                //      </embedded2>
                //    </embedded1>
                //  </Content>
                //</root>
            get {return _defaultsAndOverrides;}
            set { _defaultsAndOverrides = value; }
        }

        public XDocument MergeDefaultsAndOverridesIntoComponentDocument(XDocument input)
        {
            if (this.DefaultsAndOverrides == null) return input;
            XElement data = input.Element(Ns.tcm + "Component").Element(Ns.tcm + "Data");
            
            XElement content = data.Element(Ns.tcm + "Content");            
            var additionalContentElements = this.DefaultsAndOverrides.XPathSelectElements("root/Content/*");
            foreach (var element in additionalContentElements)
            {
                content.Add(element);
            }
            
            XElement metadata = data.Element(Ns.tcm + "Metadata"); 
            var additionalMetadataElements = this.DefaultsAndOverrides.XPathSelectElements("root/Metadata/*");
            foreach (var element in additionalMetadataElements)
            {
                metadata.Add(element);
            }

            return input;
        }

        public XDocument TransformDocumentToResult(XDocument input, string schemaID)
        {
                       
            if (string.IsNullOrEmpty(schemaID))
            {
                throw new System.ArgumentNullException("schemaID");

            }
            MergeDefaultsAndOverridesIntoComponentDocument(input);

            var args = new XsltArgumentList();
            args.AddParam("SchemaURI", string.Empty, schemaID);
            args.XsltMessageEncountered += new XsltMessageEncounteredEventHandler((o, a) => { Log("Synchroniser XSLT message: " + a.Message); });
 
            XDocument returnDocument = new XDocument();
            // need to make sure it throws nice messages if the XSLT it borked
            //StringBuilder foobar  = new StringBuilder();
            //var settings = new XmlWriterSettings();
            //settings.ConformanceLevel = ConformanceLevel.Auto;
            //XmlWriter test = XmlWriter.Create(foobar, settings);
            //this.Transform.Transform(input.CreateReader(), args, test , this.TransformResolver );
            //System.Diagnostics.Debugger.Break();

            using (XmlWriter results = returnDocument.CreateWriter()){
                try
                {
                    this.Transform.Transform(input.CreateReader(), args, results, this.TransformResolver);
                }
                catch (XsltException xsltException)
                {
                    System.Diagnostics.Debug.WriteLine(xsltException.Message);
                }
            }
            return returnDocument;           
        }

        private void Log(string message)
        {
            // TODO - wire up proper logging once we port to powertools
            System.Diagnostics.Debug.WriteLine(message);
        }
    }
}
